{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "iJUKiCXzYzYh"
   },
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install -U colorama langgraph langchain-community langchain-openai langchain-anthropic tavily-python pandas openai lancedb sentence-transformers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "0lv6tzcTY4J7"
   },
   "source": [
    "# Customer Email Agent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "aRJ318kVfSkD"
   },
   "source": [
    "## Useful Helpers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 493,
     "referenced_widgets": [
      "0b7f4530eecc4a2db19a26ff13d7ebab",
      "4c9cdeb39d944b0c9e592196f64c2243",
      "bbb9c41bb4dc4674b8285f7e480fb4f8",
      "f8ad2d01bf594381ae4ea6cd3e8b81b3",
      "6d232e0f69ed47d0ab5ebd879b212fce",
      "dbff4d216d964c9faa5ea0cda9a852a6",
      "2c50b802c8324334aabf74419f4c0380",
      "49297ce8ab3b46fcadd26ac9561b72ef",
      "3157cf0ea005456cbc2dc88dc3dfb60b",
      "a8006e354ab94b6787799fdc1458f440",
      "31eadb0402524a22be153f86a9ffe420",
      "e638726b0fb54dc3af473ff74c60b09e",
      "9f2856c0ca3d4ff998af57834e97b626",
      "8942fbe52458466ca364b3af245158c0",
      "c7c5ef64e5cb4d70a11d25e3b9564709",
      "852537ee95724d8a8435e438c71763e1",
      "b799c5c700c645af8b7b4ca26f4d3d65",
      "04f491705f4245ebac6e15252e29552c",
      "dc9b84acfdda4d50b9d69d9538adc230",
      "e3eedfdfcd9a4d7ba90e467f32862cd3",
      "990f479fd4ec4bab95c6fab21c1a98fc",
      "9f8b93878abc4882a71bfbe36af11a2a",
      "386e047c1468470d808a1ec18e713cd9",
      "2840c18dca334c37be385d27846fd1eb",
      "0ce1c5dd4d884b3781579c4286d9dca7",
      "7857c96e73f5470382d8de5332df3ba5",
      "9592ad14727f4f25a8fea3cbff1e53e8",
      "40be5ebed6d54d53ae927faf695ccb5a",
      "73131d087e6242b7954094af80c8de89",
      "e8003b3abe344dd89bd910ff9fd585ea",
      "fc8b9dfc89b549a58fa290f912019c89",
      "e36118cc1923437d9aac7b146aecc5ab",
      "4413e5afc9c742f3a63e006cd3c220a9",
      "1acddd5a488f4a84852c71a7527f2ba7",
      "0765feb32e754fd9bc3a5a3666d76b15",
      "f4305c140b3549e89f5ebe48d761efeb",
      "d3fafc521461470f8024c40558c2ed8f",
      "4f1882153c9b469397e472b19ad52530",
      "0a2ad4c2610749fb9592f447a26efeb4",
      "4e6b7c39c5ab44c2878bd8ebd2522195",
      "7e87d9ff87244663b372eacd07657d1d",
      "d40b8c0f676942b0bc39faccb21e4b6e",
      "279599ab8c8945bc8a6990fb8c14895e",
      "64b2092c0b0749c59224dd048d49da57",
      "d97d1409981c4f25ab31bacb0a35ebf4",
      "9849d298f7014022b928dcf981dcc16f",
      "a7bda393eee74d399fa2d0c374fd1282",
      "9425dc16c9804f5492ddb2c300d7646c",
      "ad1e16ec994b42a7932b8afce5d1d837",
      "775092d9682d47b4a367e35095133905",
      "7554fb7b19894c988945e245d5d80b30",
      "f4c4bc312150423486a48276f4c619e6",
      "f38f41134471494e940da086a6bbb343",
      "23026003efa547bc8678462e6bd0643d",
      "0a0eb45b893049e8a026e224c87372c4",
      "836c8bf5b34f4b3aa25ff3c55702c031",
      "457a6971993f4da38769e384523fb5ed",
      "169c19c3219d43e0b1527f6119f847fe",
      "f31e141dd6444f899420f05d0c294baa",
      "e311c79b93e2481a89720174131c73a9",
      "84d70fa920044c3aaf32a776c0ae15ac",
      "752e7287def84dcbbd29001f2a272c9b",
      "2124e4a3732e45c3bf3304dd240020f6",
      "8ffc7a70b74243488a99ea295661a642",
      "cf7b338eb66a487ca64a070e7801f770",
      "62adcca6a48543659abb7edd31f327b0",
      "326e0b65fc9f4d1097ace4279eabac9a",
      "331a134d93d24964bd2419ba63a9522e",
      "1443f1f7fd2b482c8d114abedbaf14ea",
      "a3c3cb7e0421411a84dd5c5842c233ca",
      "864de07d1c974ad5b77aaa8e081b3c0d",
      "a7be7b5f36794589932a1bc332fa064b",
      "40c16b087249494685f8e8c0a9837ba2",
      "1c3192ec44324fab920966098501c90b",
      "bfe5cf1a725741fa8f173d35d3bdce32",
      "55c7da0661e64f17ae3da66d4c6e971b",
      "34595d82524a48cda4be26e459ba9c13",
      "cdc4c590162d4c10826ccb99c0cd4cee",
      "248c02265cfe4f2da8b44f031cc83641",
      "7bcd22f768714d26a6dba8d65b05255b",
      "3b28df39004040eeb3bdfe531b7778b8",
      "d15f3845a947450a9dd6912a423aff3d",
      "0946b86f464d47f0a863e25d3db34e13",
      "82e65aa8811b47fab6369dab78970d89",
      "bf1bf15d19724f4298085dde5877e3c9",
      "806d7d52b6684075a1a31ab014565e8c",
      "c10ecc96ee2f43a58daaa4cfe292b1e4",
      "c229d9e2ef914eeb9201fa4fcfef94c9",
      "3690d8f552a5431a8c9657447c1db57d",
      "66742ac88a744a5faf3fad3b10221efd",
      "c039285ec5464ff59641dea20c8328e3",
      "1fbc2bc7185e49d18271e5ed389fb0f8",
      "42712d7e61bd4642b7cc0e08338268b6",
      "36bd62cab0b94b5c95e384f60842e954",
      "7539323cc84246d987eb4d509418f800",
      "e3b946fd51314757806dc0a935956082",
      "2e08bd7153284511b2452eafeb341dd7",
      "5ccbd94b4c694ad4a92eed4dc372ba09",
      "43b66d4ce7c7431e8ce53e69be8340bc",
      "762ca294fb3348d5b452df6df5121213",
      "b0cd015231e346fbaeec54c9a529ed14",
      "89a9fc46349a4e72a99f1d4fad5f6e6f",
      "39bc518aafcd4b1b893d12bd6ba4fd96",
      "74be4c4c5a0a4516ac654662a08b049e",
      "a76e898d57074e369ee7159cf2c0d750",
      "9ab449ae7f344c258a7a127a9a7da635",
      "fe4e0156a4ec49adadd03f014c90c93e",
      "14c3dd28c1bf49d195e851a31493d23e",
      "ee4aa2a21d5c4742ba94506f1ffbd9bd",
      "aa499516e0b94a99a8c466f7edcb4bc5",
      "f27c18f9b0954c998e4ba04052f60518",
      "e41791709935470195ab9e086f229352",
      "a12e20a4d19e4809a3963d2eeaf5e44f",
      "095fa28ee0124cafbcc94b27b20192a0",
      "4e9789597d014dc2bcce549253043300",
      "d075e21e8a0843fc8e1ae10ec5ded9a9",
      "b840bb4217d84f50adc5668407248d47",
      "e4cc04a1aa814c1db867ec5defebd285",
      "be2c8bfd5b7a40f1abcb4ca66ae0f07f",
      "e5c422c8f1774046b8d75ff6d2d11fce",
      "dc01090ee3c44423a07085a743ef30b4"
     ]
    },
    "id": "wD1HY10NfU03",
    "outputId": "b8d5dd88-0b7a-4711-c9a6-6d9debdc0969"
   },
   "outputs": [],
   "source": [
    "from langchain_core.messages import ToolMessage, SystemMessage, AIMessage, HumanMessage\n",
    "from langchain_core.runnables import RunnableLambda\n",
    "from langgraph.prebuilt import ToolNode\n",
    "from langchain_core.runnables import Runnable, RunnableConfig\n",
    "from typing import TypedDict, Annotated\n",
    "from langgraph.graph.message import AnyMessage, add_messages\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import END, StateGraph, START\n",
    "from langgraph.prebuilt import tools_condition\n",
    "import torch\n",
    "\n",
    "# llm = ChatOpenAI(model=\"gpt-3.5-turbo\")\n",
    "memory = (\n",
    "    MemorySaver()\n",
    ")  # it'll save the all the states and history corresponding to a `thread_id`. We can get previous conversations if we use memory\n",
    "\n",
    "# ------------ Vector Search ----------------\n",
    "!rm -rf \"./lancedb\"\n",
    "\n",
    "import lancedb, re, requests\n",
    "from lancedb.pydantic import LanceModel, Vector\n",
    "from lancedb.embeddings import get_registry\n",
    "import numpy as np\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "# ------- Vector DB using LanceDB ------------\n",
    "model = (\n",
    "    get_registry()\n",
    "    .get(\"sentence-transformers\")\n",
    "    .create(\n",
    "        name=\"BAAI/bge-small-en-v1.5\",\n",
    "        device=\"cuda\" if torch.cuda.is_available() else \"cpu\",\n",
    "    )\n",
    ")\n",
    "\n",
    "\n",
    "class Policy(LanceModel):\n",
    "    text: str = model.SourceField()\n",
    "    vector: Vector(model.ndims()) = model.VectorField()\n",
    "\n",
    "\n",
    "response = requests.get(\n",
    "    \"https://storage.googleapis.com/benchmarks-artifacts/travel-db/swiss_faq.md\"\n",
    ")\n",
    "response.raise_for_status()\n",
    "faq_text = response.text\n",
    "\n",
    "\n",
    "class VectorStoreRetriever:\n",
    "    def __init__(\n",
    "        self,\n",
    "        db_path: str,\n",
    "        table_name: str,\n",
    "        model,\n",
    "        docs: list,\n",
    "        schema,\n",
    "    ):\n",
    "        self.db = lancedb.connect(db_path)\n",
    "        self.table = self.db.create_table(table_name, schema=schema)\n",
    "        self.table.add([{\"text\": txt} for txt in re.split(r\"(?=\\n##)\", faq_text)])\n",
    "\n",
    "    def query(self, query: str, k: int = 5) -> list[dict]:\n",
    "        result = self.table.search(query).limit(k).to_list()\n",
    "        return [\n",
    "            {\"page_content\": item[\"text\"], \"similarity\": 1 - item[\"_distance\"]}\n",
    "            for item in result\n",
    "        ]\n",
    "\n",
    "\n",
    "retriever = VectorStoreRetriever(\"./lancedb\", \"company_policy\", model, faq_text, Policy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "id": "VasUJ8L0qE2w",
    "outputId": "b3036d68-7565-4ad3-83d9-4a6c80ab4c2f"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[32mStarting workflow...\u001b[0m\n",
      "\u001b[36mFinished running: fetch_emails\u001b[0m\n",
      "\u001b[36mFinished running: process_next_email\u001b[0m\n",
      "\u001b[36mFinished running: draft_email\u001b[0m\n",
      "\u001b[36mFinished running: validate_draft\u001b[0m\n",
      "\u001b[36mFinished running: draft_email\u001b[0m\n",
      "\n",
      "\n",
      "-----------------------Sending Email ---------------\n",
      "\n",
      "\n",
      "\u001b[36mFinished running: validate_draft\u001b[0m\n",
      "\n",
      "\n",
      "Sending email: Dear Mr. Chen,\n",
      "\n",
      "Thank you for reaching out to us and for choosing SWISS for your travel needs.\n",
      "\n",
      "I'm pleased to inform you that you can indeed upgrade your Economy Flex fare to Business Class. To do so, you may visit our website at swiss.com or contact the SWISS Service Center, where our team will be happy to assist you with the upgrade process. Please note that the upgrade will include many of the benefits offered in Business Class.\n",
      "\n",
      "If you have any further questions or require additional assistance, please do not hesitate to contact us.\n",
      "\n",
      "We look forward to welcoming you on board.\n",
      "\n",
      "Kind regards,\n",
      "\n",
      "Saleem Shady  \n",
      "SWISS Customer Service Team\n",
      "\u001b[36mFinished running: send_or_skip_email\u001b[0m\n",
      "\u001b[36mFinished running: process_next_email\u001b[0m\n",
      "\u001b[36mFinished running: draft_email\u001b[0m\n",
      "\u001b[36mFinished running: validate_draft\u001b[0m\n",
      "\u001b[36mFinished running: draft_email\u001b[0m\n",
      "\u001b[36mFinished running: validate_draft\u001b[0m\n",
      "\u001b[36mFinished running: draft_email\u001b[0m\n",
      "\n",
      "\n",
      "*********************** Draft Failed after Max Tries ******************** \n",
      "\n",
      "\n",
      "\u001b[36mFinished running: validate_draft\u001b[0m\n",
      "\u001b[36mFinished running: send_or_skip_email\u001b[0m\n",
      "\u001b[36mFinished running: process_next_email\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "from typing import Optional, List\n",
    "from pydantic import BaseModel\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_openai import AzureChatOpenAI\n",
    "from langgraph.graph import END, StateGraph, START\n",
    "import os\n",
    "from dotenv import load_dotenv\n",
    "import random\n",
    "from typing import Annotated\n",
    "from langgraph.graph.message import AnyMessage, add_messages\n",
    "from typing_extensions import TypedDict\n",
    "from langchain_core.messages import ToolMessage, SystemMessage, AIMessage, HumanMessage\n",
    "from langchain_core.runnables import RunnableLambda\n",
    "from langgraph.prebuilt import ToolNode\n",
    "from langchain_core.runnables import Runnable, RunnableConfig\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.prebuilt import tools_condition\n",
    "import lancedb, re, requests\n",
    "from lancedb.pydantic import LanceModel, Vector\n",
    "from lancedb.embeddings import get_registry\n",
    "import numpy as np\n",
    "from langchain_core.tools import tool\n",
    "from google.colab import userdata\n",
    "import os\n",
    "from colorama import Fore, Style\n",
    "\n",
    "memory = (\n",
    "    MemorySaver()\n",
    ")  # it'll save the all the states and history corresponding to a `thread_id`. We can get previous conversations if we use memory\n",
    "\n",
    "\n",
    "class Email(BaseModel):\n",
    "    id: str\n",
    "    sender: str\n",
    "    subject: str\n",
    "    body: str\n",
    "    final_reply: str = \"\"\n",
    "    status: str = \"pending\"  # pending, sent, failed, skipped\n",
    "    failure_reason: str = \"\"\n",
    "\n",
    "\n",
    "class EmailState(BaseModel):\n",
    "    emails: List[Email] = []\n",
    "    processed_emails: List[Email] = []\n",
    "    current_email: Optional[Email] = None\n",
    "    policy_context: Optional[str] = \"\"\n",
    "    draft: str = \"\"\n",
    "    trials: int = 0\n",
    "    allowed_trials: int = 3\n",
    "    sendable: bool = False\n",
    "    exit: bool = False\n",
    "\n",
    "\n",
    "def create_dummy_random_emails():\n",
    "    items = [\n",
    "        {\n",
    "            \"subject\": \"Invoice Request for Recent Flight Booking\",\n",
    "            \"body\": \"Hey SWISS Team, I recently booked a flight with you (Booking Reference: LX123456) and need an invoice for my records. Could you let me know how to get it? Thanks! Anna Müller\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Rebooking Inquiry for Upcoming Flight\",\n",
    "            \"body\": \"Hi there, I need to change the dates for my flight (Booking Reference: LX789012). Can you let me know if this is possible and what fees I might have to pay? Cheers, John Smith\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Cancellation of Flight LX345678\",\n",
    "            \"body\": \"Hi SWISS, I need to cancel my flight (Booking Reference: LX345678) because of some unexpected changes. Can you walk me through the cancellation process and any fees? Thanks, Maria Gonzalez\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Request for Special Invoice for Italy\",\n",
    "            \"body\": \"Hi SWISS, I booked a flight from Italy and need a special invoice for tax reasons. Can you help me out with this? Thanks, Luca Rossi\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Payment Issue with Credit Card\",\n",
    "            \"body\": \"Hey, I tried paying for my booking with my Visa card, but it didn’t go through. Can you check if it’s an issue with my card or your system? Thanks, Emily Brown\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Refund Status for Cancelled Flight\",\n",
    "            \"body\": \"Hi SWISS, I cancelled my flight (Booking Reference: LX456789) a couple of weeks ago and was told I’d get a refund. Any updates on that? Thanks, David Johnson\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Seat Reservation Inquiry\",\n",
    "            \"body\": \"Hey, I have a booking (Reference: LX567890) and wanted to check if my seat reservation will stay the same after a rebooking. Let me know! Sophie Lee\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Upgrade Request for Economy Flex Fare\",\n",
    "            \"body\": \"Hi SWISS, I booked an Economy Flex fare and was wondering if I can upgrade to Business Class. Can you let me know how to do that? Thanks, Michael Chen\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Group Booking Inquiry\",\n",
    "            \"body\": \"Hi, I’m looking to book flights for a group of 12 people. Can you give me some info on group booking options and any discounts? Thanks, Sarah Wilson\",\n",
    "        },\n",
    "        {\n",
    "            \"subject\": \"Issue with Online Booking Platform\",\n",
    "            \"body\": \"Hey SWISS, I can’t see my recent booking in my profile on your website. Can you help me fix this? Thanks, Thomas Anderson\",\n",
    "        },\n",
    "    ]\n",
    "    chosen_items = [random.choice(items) for _ in range(random.randint(0, 2))]\n",
    "    return [\n",
    "        Email(\n",
    "            id=str(i),\n",
    "            sender=\"some_user@example.mail\",\n",
    "            subject=item[\"subject\"],\n",
    "            body=item[\"body\"],\n",
    "        )\n",
    "        for i, item in enumerate(chosen_items)\n",
    "    ]\n",
    "\n",
    "\n",
    "class EmailAgent:\n",
    "    def __init__(self):\n",
    "        self.llm = ChatOpenAI(model=\"gpt-3.5-turbo\")  # Replace with your LLM you want\n",
    "\n",
    "    def fetch_unread_emails(self) -> List[Email]:\n",
    "        \"\"\"\n",
    "        Replace this with your Email LOGIC\n",
    "        \"\"\"\n",
    "        return create_dummy_random_emails()\n",
    "\n",
    "    def lookup_policy(self, subject: str, body: str) -> str:\n",
    "        \"\"\"Always Consult the company policies to answer the queries.\n",
    "        Use this for drafting the emails\"\"\"\n",
    "        prompt = PromptTemplate(\n",
    "            template=\"Identify whether the given email is policy related or not. Identify if the email requires info which might be in the policy documents.\\n\\nSubject: {subject}\\n\\nBody:\\n{body}\\n\\n. Do not output any reasoning etc. Strictly reply with Yes/No\",\n",
    "            input_variables=[\"email\"],\n",
    "        )\n",
    "        chain = prompt | self.llm\n",
    "        response = chain.invoke({\"subject\": subject, \"body\": body})\n",
    "        policy_related = response.content.strip().lower() == \"yes\"\n",
    "        if policy_related:\n",
    "            docs = retriever.query(\n",
    "                f\"Email Subject: {subject}\\n\\nEmail Body:\\n{body}\", k=2\n",
    "            )\n",
    "            return \"\\nPolicy Context:\" + \"\\n\\n\".join(\n",
    "                [doc[\"page_content\"] for doc in docs]\n",
    "            )\n",
    "        return \"\"\n",
    "\n",
    "    def draft_email(\n",
    "        self, email_subject: str, email_body: str, email_context: str = \"\"\n",
    "    ) -> str:\n",
    "        if not email_context:\n",
    "            prompt = PromptTemplate(\n",
    "                template=\"You are a specialised chat agent named Saleem Shady' working for SWISS Airline. Write a well professional response to this user email:\\n\\nEmail Subject: {email_subject}\\n\\nEmail Body:\\n{email_body}\\n\\nReply with a perfect response, nothing else\\nResponse:\",\n",
    "                input_variables=[\"email\"],\n",
    "            )\n",
    "        else:\n",
    "            prompt = PromptTemplate(\n",
    "                template=\"You are a specialised chat agent named Saleem Shady' working for SWISS Airline. Write a well professional response to this user email given the Context (which may or may not be required in answering)\\n\\n{email_context}\\n\\nEmail Subject: {email_subject}\\n\\nEmail Body:\\n{email_body}\\n\\nReply with a perfect response, nothing else\\nResponse:\",\n",
    "                input_variables=[\"email\"],\n",
    "            )\n",
    "\n",
    "        chain = prompt | self.llm\n",
    "        response = chain.invoke(\n",
    "            {\n",
    "                \"email_subject\": email_subject,\n",
    "                \"email_body\": email_body,\n",
    "                \"email_context\": email_context,\n",
    "            }\n",
    "        )\n",
    "        return response.content\n",
    "\n",
    "    def validate_draft(self, initial_email: str, draft_email: str) -> bool:\n",
    "        prompt = PromptTemplate(\n",
    "            template=\"You are a Email Proofreader. Review this response:\\n\\nOriginal Email:\\n{initial_email}\\n\\nDraft Response:\\n{draft_email}\\n\\nIs this mail ready to send? Do not give your reasoning or views. Reply only with (Yes/No):\",\n",
    "            input_variables=[\"initial_email\", \"draft_email\"],\n",
    "        )\n",
    "        chain = prompt | self.llm\n",
    "        response = chain.invoke(\n",
    "            {\"initial_email\": initial_email, \"draft_email\": draft_email}\n",
    "        )\n",
    "        return response.content.strip().lower() == \"yes\"\n",
    "\n",
    "\n",
    "def create_workflow():\n",
    "    workflow = StateGraph(EmailState)\n",
    "    agent = EmailAgent()\n",
    "\n",
    "    def fetch_emails(state: EmailState) -> EmailState:\n",
    "        emails = agent.fetch_unread_emails()\n",
    "        state.emails = emails\n",
    "        return state\n",
    "\n",
    "    def process_next_email(state: EmailState) -> EmailState:\n",
    "        if state.emails:\n",
    "            state.current_email = state.emails.pop(0)\n",
    "            state.policy_context = agent.lookup_policy(\n",
    "                state.current_email.subject, state.current_email.body\n",
    "            )\n",
    "        else:\n",
    "            state.exit = True\n",
    "        return state\n",
    "\n",
    "    def draft_email(state: EmailState) -> EmailState:\n",
    "        if state.current_email:\n",
    "            state.draft = agent.draft_email(\n",
    "                state.current_email.subject,\n",
    "                state.current_email.body,\n",
    "                state.policy_context,\n",
    "            )\n",
    "            state.trials += 1\n",
    "        return state\n",
    "\n",
    "    def validate_draft(state: EmailState) -> EmailState:\n",
    "        if state.current_email and state.draft:\n",
    "            state.sendable = agent.validate_draft(state.current_email.body, state.draft)\n",
    "        return state\n",
    "\n",
    "    def decide_next_step(state: EmailState) -> str:\n",
    "        if state.sendable:\n",
    "            print(\"\\n\\n-----------------------Sending Email ---------------\\n\\n\")\n",
    "            return \"send\"\n",
    "        elif state.trials >= state.allowed_trials:\n",
    "            state.current_email.status = \"failed\"\n",
    "            state.current_email.failure_reason = \"Failed after 3 attempts\"\n",
    "            print(\n",
    "                \"\\n\\n*********************** Draft Failed after Max Tries ******************** \\n\\n\"\n",
    "            )\n",
    "            return \"stop\"\n",
    "        else:\n",
    "            return \"rewrite\"\n",
    "\n",
    "    def send_or_skip_email(state: EmailState) -> EmailState:\n",
    "        if state.current_email.status != \"failed\":\n",
    "            print(f\"\\n\\nSending email: {state.draft}\")\n",
    "            state.current_email.final_reply = state.draft\n",
    "            state.current_email.status = \"sent\"\n",
    "            state.processed_emails.append(state.current_email)\n",
    "\n",
    "        # Reset state for the next email\n",
    "        state.current_email = None\n",
    "        state.draft = \"\"\n",
    "        state.trials = 0\n",
    "        state.policy_context = \"\"\n",
    "        return state\n",
    "\n",
    "    workflow.add_node(\"fetch_emails\", fetch_emails)\n",
    "    workflow.add_node(\"process_next_email\", process_next_email)\n",
    "    workflow.add_node(\"draft_email\", draft_email)\n",
    "    workflow.add_node(\"validate_draft\", validate_draft)\n",
    "    workflow.add_node(\"send_or_skip_email\", send_or_skip_email)\n",
    "\n",
    "    workflow.add_edge(START, \"fetch_emails\")\n",
    "    workflow.add_edge(\"fetch_emails\", \"process_next_email\")\n",
    "\n",
    "    workflow.add_conditional_edges(\n",
    "        \"process_next_email\",\n",
    "        lambda state: END if state.exit else \"draft_email\",\n",
    "        {\"draft_email\": \"draft_email\", END: END},\n",
    "    )\n",
    "\n",
    "    workflow.add_edge(\"draft_email\", \"validate_draft\")\n",
    "\n",
    "    workflow.add_conditional_edges(\n",
    "        \"validate_draft\",\n",
    "        decide_next_step,\n",
    "        {\n",
    "            \"send\": \"send_or_skip_email\",\n",
    "            \"rewrite\": \"draft_email\",\n",
    "            \"stop\": \"send_or_skip_email\",\n",
    "        },\n",
    "    )\n",
    "\n",
    "    workflow.add_edge(\"send_or_skip_email\", \"process_next_email\")\n",
    "\n",
    "    return workflow.compile()\n",
    "\n",
    "\n",
    "compiled_email_subgraph = create_workflow()\n",
    "initial_state = EmailState()\n",
    "\n",
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(compiled_email_subgraph.get_graph(xray=True).draw_mermaid_png()))\n",
    "except Exception:\n",
    "    pass\n",
    "\n",
    "\n",
    "print(Fore.GREEN + \"Starting workflow...\" + Style.RESET_ALL)\n",
    "for output in compiled_email_subgraph.stream(initial_state):\n",
    "    for key, value in output.items():\n",
    "        print(Fore.CYAN + f\"Finished running: {key}\" + Style.RESET_ALL)\n",
    "        # print(Fore.YELLOW + f\"State after {key}:\" + Style.RESET_ALL)\n",
    "        # print(value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "84NJOT-u4Xh3"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
